<?php

/**
 * Adapted by Darren Stephens <darren.stephens@durham.ac.uk>
 * from Java implementation of Telepen by <rstuart114@gmail.com> Robin Stuart
 * at https://github.com/woo-j/OkapiBarcode which uses the
 * Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
 *
 * Implements Telepen (also known as Telepen Alpha), and Telepen Numeric.
 *
 * Telepen can encode ASCII text input and includes a modulo-127 check digit.
 * Telepen Numeric allows compression of numeric data into a Telepen symbol. Data
 * can consist of pairs of numbers or pairs consisting of a numerical digit followed
 * by an X character. Telepen Numeric also includes a mod-127 check digit.
 */

namespace zxf\BarCode\Types;

use zxf\BarCode\Barcode;
use zxf\BarCode\BarcodeBar;
use zxf\BarCode\Exceptions\InvalidFormatException;

class TypeTelepen implements TypeInterface
{
    private const TELEPEN_START_CHAR = '_';

    private const TELEPEN_STOP_CHAR = 'z';

    private const TELEPEN_ALPHA = 'alpha';

    private const TELEPEN_NUMERIC = 'numeric';

    private array $telepen_lookup_table;

    private string $mode;

    public function __construct($m = 'alpha')
    {
        $this->mode = self::TELEPEN_ALPHA;
        if (strtolower($m) == 'numeric') {
            $this->mode = self::TELEPEN_NUMERIC;
        }
        $this->createTelepenConversionTable();
    }

    public function getBarcode(string $code): Barcode
    {
        /* The stream we get from the telepen output gives us the
         * width of alternating black/white stripes
         */

        $encoded = $this->encode($code); // binary string
        $barcode = new Barcode($code);

        $drawBar = true;
        for ($i = 0; $i < strlen($encoded); $i++) {
            $barWidth = $encoded[$i];
            $barcode->addBar(new BarcodeBar(intval($barWidth), 250, $drawBar));
            $drawBar = ! $drawBar; // flip to other colour
        }

        return $barcode;
    }

    protected function encode($code): string
    {
        if ($this->mode == self::TELEPEN_ALPHA) {
            $result = $this->encodeAlpha($code);
        } else {
            $result = $this->encodeNumeric($code);
        }

        return $result;
    }

    protected function encodeAlpha($code): string
    {
        // We aren't interested in the non-printable parts of the ASCII set
        if (
            ! preg_match('/[ -~]+/', $code)
        ) { // everything from ASCII32-ASCII127
            throw new InvalidFormatException('Invalid characters in data');
        }

        $count = 0;

        /* other implementations use the byte-chr-int type equivalence to work
         * with array indices in the conversion/lookup table. It's probably
         * better to be more explicit with php, hence the use of ord and chr here.
         */

        // begin with start char
        $dest = $this->telepen_lookup_table[ord(self::TELEPEN_START_CHAR)];

        for ($i = 0; $i < strlen($code); $i++) {
            // $ascii_code = ord(substr($code, $i, 1));
            $ascii_code = ord($code[$i]);
            $dest .= ($this->telepen_lookup_table[$ascii_code]);
            $count += $ascii_code;
        }

        // Now add check and terminator
        $check_digit = 127 - ($count % 127);
        if ($check_digit == 127) {
            $check_digit = 0;
        }

        $dest .= $this->telepen_lookup_table[ord(strval($check_digit))];
        $dest .= $this->telepen_lookup_table[ord(self::TELEPEN_STOP_CHAR)]; // Stop

        return $dest;
    }

    private function encodeNumeric(string $code): string
    {

        /* If input contains non-numeric or X, exit */
        if (! preg_match('/^[0-9X]+$/', $code)) {
            throw new InvalidFormatException('Invalid characters in data');
        }

        /* If input is an odd length, exit */
        $t = '';
        if (strlen($code) % 2 > 0) {
            throw new InvalidFormatException('There must be an even number of digits');
        }

        $count = 0;
        $dest = $this->telepen_lookup_table[ord(self::TELEPEN_START_CHAR)]; // begin with the start character _

        for ($i = 0; $i < strlen($code); $i += 2) {
            $c1 = $code[$i];
            $c2 = $code[$i + 1];
            /* Input nX is allowed, but Xn is not */
            if ($c1 == 'X') {
                throw new InvalidFormatException('Invalid position of X in data');
            }
            $glyph = null;
            if ($c2 == 'X') {
                $glyph = (ord($c1) - ord('0')) + 17;
            } else {
                $glyph = ((10 * (ord($c1) - ord('0'))) + (ord($c2) - ord('0'))) + 27;
            }
            $count += $glyph;
            $dest .= $this->telepen_lookup_table[$glyph];
        }

        $check_digit = 127 - ($count % 127);
        if ($check_digit == 127) {
            $check_digit = 0;
        }

        $dest .= $this->telepen_lookup_table[$check_digit];
        $dest .= $this->telepen_lookup_table[ord(self::TELEPEN_STOP_CHAR)]; // Stop

        return $dest;
    }

    /**
     * The table provides a representation of barcode patterns
     * for each character in the ASCII set. from ASCII0-127
     * Each barcode starts with "_" - ASCII95 0x5F,
     * and ends with "z" - ASCII122 0xFA.
     * More information about Telepen symbology is available from
     * https://v4l237.n3cdn1.secureserver.net/wp-content/uploads/2022/05/Barcode-Symbology-information-and-History.pdf
     */
    private function createTelepenConversionTable(): void
    {
        $this->telepen_lookup_table = [
            '1111111111111111', '1131313111', '33313111', '1111313131',
            '3111313111', '11333131', '13133131', '111111313111',
            '31333111', '1131113131', '33113131', '1111333111',
            '3111113131', '1113133111', '1311133111', '111111113131',
            '3131113111', '11313331', '333331', '111131113111',
            '31113331', '1133113111', '1313113111', '1111113331',
            '31131331', '113111113111', '3311113111', '1111131331',
            '311111113111', '1113111331', '1311111331', '11111111113111',
            '31313311', '1131311131', '33311131', '1111313311',
            '3111311131', '11333311', '13133311', '111111311131',
            '31331131', '1131113311', '33113311', '1111331131',
            '3111113311', '1113131131', '1311131131', '111111113311',
            '3131111131', '1131131311', '33131311', '111131111131',
            '3111131311', '1133111131', '1313111131', '111111131311',
            '3113111311', '113111111131', '3311111131', '111113111311',
            '311111111131', '111311111311', '131111111311', '11111111111131',
            '3131311111', '11313133', '333133', '111131311111',
            '31113133', '1133311111', '1313311111', '1111113133',
            '313333', '113111311111', '3311311111', '11113333',
            '311111311111', '11131333', '13111333', '11111111311111',
            '31311133', '1131331111', '33331111', '1111311133',
            '3111331111', '11331133', '13131133', '111111331111',
            '3113131111', '1131111133', '33111133', '111113131111',
            '3111111133', '111311131111', '131111131111', '111111111133',
            '31311313', '113131111111', '3331111111', '1111311313',
            '311131111111', '11331313', '13131313', '11111131111111',
            '3133111111', '1131111313', '33111313', '111133111111',
            '3111111313', '111313111111', '131113111111', '111111111313',
            '313111111111', '1131131113', '33131113', '11113111111111',
            '3111131113', '113311111111', '131311111111', '111111131113',
            '3113111113', '11311111111111', '331111111111', '111113111113',
            '31111111111111', '111311111113', '131111111113',
        ];
    }
}
